﻿// ***********************************************************************
// Assembly         : DonNicky.Common
// Author           : donnicky
// Created          : 11-19-2012
//
// Last Modified By : donnicky
// Last Modified On : 11-16-2012
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Microsoft.CSharp.RuntimeBinder;

namespace DonNicky.Common.Xml
{
    /// <summary>
    /// DynamicXmlElement class allows XML file parsing in a dynamic object style.
    /// </summary>
    public class DynamicXmlElement : DynamicObject, IList<DynamicXmlElement>
    {
        /// <summary>
        /// The element which properties will be read.
        /// </summary>
        private readonly XElement _element;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicXmlElement" /> class.
        /// </summary>
        /// <param name="element">The XML element.</param>
        public DynamicXmlElement(XElement element)
        {
            _element = element;
        }

        /// <summary>
        /// Tries the get member of the element.
        /// </summary>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var binderName = binder.Name;
            var optional = binderName.Length > 5 && binderName.Substring(0, 5) == "_Opt_";
            if (optional)
                binderName = binderName.Substring(5);
            var elements = _element.Elements(binderName).ToList();
            var attribute = _element.Attribute(binderName);
            var count = elements.Count();

            if (count == 0)
            {
                if (attribute != null)
                {
                    result = attribute.Value;
                    return true;
                }

                try
                {
                    result = GetElementMember(binderName);
                }
                catch (RuntimeBinderException)
                {
                    if (optional)
                    {
                        result = new AbsentXmlElement();
                        return true;
                    }
                    result = null;
                    return false;
                }

                return true;
            }

            if (count == 1)
            {
                result = new DynamicXmlElement(elements.First());
                return true;
            }

            // добавить атрибуты
            result = new List<DynamicXmlElement>(elements.Select(x => new DynamicXmlElement(x)));
            return true;
        }

        private object GetElementMember(string memberName)
        {
            var elementBinder =
                Binder.GetMember(
                    CSharpBinderFlags.None,
                    memberName,
                    typeof(XElement),
                    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });

            var callsite = CallSite<Func<CallSite, XElement, object>>.Create(elementBinder);

            return callsite.Target(callsite, _element);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="DynamicXmlElement" /> to <see cref="XElement" />.
        /// </summary>
        public static implicit operator XElement(DynamicXmlElement element) { return element._element; }

        /// <summary>
        /// Performs an implicit conversion from <see cref="DynamicXmlElement" /> to <see cref="System.String" />.
        /// </summary>
        public static implicit operator string(DynamicXmlElement element) { return element.ToString(); }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        public override string ToString()
        {
            return _element.Value;
        }

        #region IList<DynamicXmlElement> Members

        /// <summary>
        /// Not implemented.
        /// </summary>
        public int IndexOf(DynamicXmlElement item) { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented.
        /// </summary>
        public void Insert(int index, DynamicXmlElement item) { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented.
        /// </summary>
        public void RemoveAt(int index) { throw new NotImplementedException(); }

        /// <summary>
        /// Gets the <see cref="DynamicXmlElement" /> at the specified index. Setter not implemented.
        /// </summary>
        /// <param name="index">The index.</param>
        public DynamicXmlElement this[int index] { get { if (index == 0)return this; throw new IndexOutOfRangeException(); } set { throw new NotImplementedException(); } }

        #endregion

        #region ICollection<DynamicXmlElement> Members

        /// <summary>
        /// Not implemented.
        /// </summary>
        public void Add(DynamicXmlElement item) { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented.
        /// </summary>
        public void Clear() { throw new NotImplementedException(); }

        /// <summary>
        /// Determines whether [contains] [the specified item].
        /// </summary>
        public bool Contains(DynamicXmlElement item) { return item == this; }

        /// <summary>
        /// Copies to.
        /// </summary>
        /// <param name="array">The array.</param>
        /// <param name="arrayIndex">Index of the array.</param>
        public void CopyTo(DynamicXmlElement[] array, int arrayIndex) { array[arrayIndex] = this; }

        /// <summary>
        /// Returns 1.
        /// </summary>
        public int Count { get { return 1; } }

        /// <summary>
        /// Always true.
        /// </summary>
        public bool IsReadOnly { get { return true; } }

        /// <summary>
        /// Not implemented.
        /// </summary>
        public bool Remove(DynamicXmlElement item) { throw new NotImplementedException(); }

        #endregion

        #region IEnumerable<DynamicXmlElement> Members

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns>IEnumerator{DynamicXmlElement}.</returns>
        public IEnumerator<DynamicXmlElement> GetEnumerator() { return new[] { this }.AsEnumerable().GetEnumerator(); }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new[] { this }.GetEnumerator(); }

        #endregion
    }

    /// <summary>
    /// Represents absent xml element
    /// </summary>
    public class AbsentXmlElement : DynamicObject
    {
        /// <summary>
        /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" /> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" /> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
        /// <param name="result">The result is itself.</param>
        /// <returns>Always true.</returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = this;
            return true;
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="AbsentXmlElement" /> to <see cref="System.String" />.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator string(AbsentXmlElement element) { return element.ToString(); }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>Empty string.</returns>
        public override string ToString()
        {
            return string.Empty;
        }
    }
}
